Go 并发编程-9.Context

Context

包 context 定义了 Context 接口,Context 的具体实现包括 4 个方法,分别是 Deadline、Done、Err 和 Value

1
2
3
4
5
6
7
8
9
type Context interface {
// 方法会返回这个 Context 被取消的截止日期。如果没有设置截止日期,ok 的值是 false。后续每次调用这个对象的 Deadline 方法时,都会返回和第一次调用相同的结果。
Deadline() (deadline time.Time, ok bool)
// 方法返回一个 Channel 对象。在 Context 被取消时,此 Channel 会被 close,如果没被取消,可能会返回 nil。
Done() <-chan struct{}
Err() error
// 返回此 ctx 中和指定的 key 相关联的 value
Value(key interface{}) interface{}
}

Context 中实现了 2 个常用的生成顶层 Context 的方法。context.Background() 和 context.TODO()。

WithValue

WithValue 基于 parent Context 生成一个新的 Context,保存了一个 key-value 键值对。它常常用来传递上下文。WithValue 方法其实是创建了一个类型为 valueCtx 的 Context,它的类型定义如下:

1
2
3
4
type valueCtx struct {
Context
key, val interface{}
}

它覆盖了 Value 方法,优先从自己的存储中检查这个 key,不存在的话会从 parent 中继续检查。

1
2
3
4
5
6
7
ctx = context.TODO()
ctx = context.WithValue(ctx, "key1", "0001")
ctx = context.WithValue(ctx, "key2", "0001")
ctx = context.WithValue(ctx, "key3", "0001")
ctx = context.WithValue(ctx, "key4", "0004")

fmt.Println(ctx.Value("key1"))

WithCancel

WithCancel 方法返回 parent 的副本,只是副本中的 Done Channel 是新建的对象,它的类型是 cancelCtx。

1
2
3
4
5
6
7
8
9
10
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)// 把c朝上传播
return &c, func() { c.cancel(true, Canceled) }
}

// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}

代码中调用的 propagateCancel 方法会顺着 parent 路径往上找,直到找到一个 cancelCtx,或者为 nil。如果不为空,就把自己加入到这个 cancelCtx 的 child,以便这个 cancelCtx 被取消的时候通知自己。如果为空,会新起一个 goroutine,由它来监听 parent 的 Done 是否已关闭。

cancel 是向下传递的,如果一个 WithCancel 生成的 Context 被 cancel 时,如果它的子 Context(也有可能是孙,或者更低,依赖子的类型)也是 cancelCtx 类型的,就会被 cancel,但是不会向上传递。parent Context 不会因为子 Context 被 cancel 而 cancel。

WithTimeout

WithTimeout 其实是和 WithDeadline 一样,只不过一个参数是超时时间,一个参数是截止时间

1
2
3
4
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
// 当前时间+timeout就是deadline
return WithDeadline(parent, time.Now().Add(timeout))
}

WithDeadline

WithDeadline 会返回一个 parent 的副本,并且设置了一个不晚于参数 d 的截止时间,类型为 timerCtx(或者是 cancelCtx)。

如果它的截止时间晚于 parent 的截止时间,那么就以 parent 的截止时间为准,并返回一个类型为 cancelCtx 的 Context,因为 parent 的截止时间到了,就会取消这个 cancelCtx。如果当前时间已经超过了截止时间,就直接返回一个已经被 cancel 的 timerCtx。

否则就会启动一个定时器,到截止时间取消这个 timerCtx。

综合起来,timerCtx 的 Done 被 Close 掉,主要是由下面的某个事件触发的:

  • 截止时间到了;
  • cancel 函数被调用;
  • parent 的 Done 被 close。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
// 如果parent的截止时间更早,直接返回一个cancelCtx即可
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c) // 同cancelCtx的处理逻辑
dur := time.Until(d)
if dur <= 0 { //当前时间已经超过了截止时间,直接cancel
c.cancel(true, DeadlineExceeded)
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
// 设置一个定时器,到截止时间后取消
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}

总结